
#include "TerrainTexture.h"


////////////////////////////////////////////////////////////////////////////////
/*----------------------------------------------------------------------------*/
TerrainTexture::TerrainTexture() {

	glGenTextures( 1, &uiTexId );
	glBindTexture( GL_TEXTURE_2D, uiTexId );

	glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );

	generate();
	
	float* texData = new float[iWidth*iHeight];

	for ( int u = 0 ; u < iWidth ; u++ ) {	
		for ( int v = 0 ; v < iHeight ; v++ ) {
			texData[u+v*iWidth] = fElevData[u][v];
		}
	}

	
	gluBuild2DMipmaps(GL_TEXTURE_2D, 1, iWidth, iHeight, GL_LUMINANCE, 
		GL_FLOAT, texData);

	delete[] fElevData;
	delete[] texData;  
}

/*----------------------------------------------------------------------------*/
inline int TerrainTexture::getWrappedU(int& u) {
	if ( u < 0 ) { return iWidth+u; }
	if ( u >= iWidth ) { return u-iWidth; }
	return u;
}

/*----------------------------------------------------------------------------*/
inline int TerrainTexture::getWrappedV(int& v) {
	if ( v < 0 ) { return iHeight+v; }
	if ( v >= iHeight ) { return v-iHeight; }
	return v;
}

/*----------------------------------------------------------------------------*/
void TerrainTexture::generate() {
	
	iWidth = 128;
	iHeight = 64;

	fElevData = new float*[iWidth];
	for ( int u = 0 ; u < iWidth ; u++ ) { 
		fElevData[u] = new float[iHeight];
		for ( int v = 0 ; v < iHeight ; v++ ) {
			fElevData[u][v] = 0;
		}
	}
	fElevMax = fElevMin = 0;

	int iter = 1200;
	float maxH = 1.0f;
	float maxRad = min(iWidth,iHeight)*0.25f;
	float mult = pow(0.1f,(1/(float)iter));

	for ( int i = 0 ; i < iter ; i++ ) {
		bubble(maxH, maxRad);
		maxH *= mult;
		maxRad *= mult;
	}

	normalize();
	blendOutPoles();
}

/*----------------------------------------------------------------------------*/
void TerrainTexture::bubble(float h, float radf) {

	// This function raises/lowers a bubble-like shape on the terrain with a 
	// random change in range [-h, h] with radius radf.

	float delta = getRandFRange(-h,h);
	int rad = (int)radf;
	int radsqr = rad*rad;
	int cu = rand()%iWidth;
	int cv = rand()%iHeight;
	int testU, testV, wrapU, wrapV, mag;
	float percent;

	for ( int u = -rad ; u <= rad ; u++ ) {
		for ( int v = -rad ; v <= rad ; v++ ) {

			testU = cu+u;
			testV = cv+v;

			mag = u*u + v*v;
			if ( mag >= radsqr ) { continue; } // Skip if outside radius

			wrapU = getWrappedU( testU );
			wrapV = getWrappedV( testV );

			percent = 1-(mag/(float)radsqr);
			float &e = fElevData[wrapU][wrapV];
			e += delta*percent;

			if ( e > fElevMax ) { fElevMax = e; }
			else if ( e < fElevMin ) { fElevMin = e;}
		}
	}
}

/*----------------------------------------------------------------------------*/
void TerrainTexture::polarize() {

	// Derived from:
	// http://local.wasp.uwa.edu.au/~pbourke/texture_colour/polargrid/

	float theta, phi, phi2;
	int u2;
	float PI = 3.14159265f;

	float** elevOrig = new float*[iWidth];

	for ( int u = 0 ; u < iWidth ; u++ ) {
		elevOrig[u] = new float[iHeight];
		for ( int v = 0 ; v < iHeight ; v++ ) {	
			elevOrig[u][v] = fElevData[u][v];
		}
	}

	for ( int v = 0 ; v < iHeight ; v++ ) {

		theta = PI * (v-(iHeight-1)*0.5f) / (iHeight-1);

		for ( int u = 0 ; u < iWidth ; u++ ) {

			phi  = 2*PI*(u-iWidth*0.5f) / iWidth;
			phi2 = phi*cos(theta);
			u2   = (int)(phi2*iWidth / (2*PI)) + iWidth/2;
			
			fElevData[u][v] = elevOrig[u2][v];
		}
	}
}

/*----------------------------------------------------------------------------*/
void TerrainTexture::blendOutPoles() {

	int distFromPole;
	int maxDist = iHeight/4;
	float blendPower;

	for ( int v = 0 ; v < iHeight ; v++ ) {

		distFromPole = min(v, iHeight-v);
		if ( distFromPole > maxDist ) { continue; }

		blendPower = 1-(distFromPole/(float)maxDist);

		for ( int u = 0 ; u < iWidth ; u++ ) {	
			float &e = fElevData[u][v];
			e = 0.5f*blendPower + e*(1-blendPower);
 		}
	}
}

/*----------------------------------------------------------------------------*/
void TerrainTexture::normalize() {
	
	float diffInv = 1/(fElevMax-fElevMin);

	for ( int u = 0 ; u < iWidth ; u++ ) {	
		for ( int v = 0 ; v < iHeight ; v++ ) {
			float &e = fElevData[u][v];
			e = (e-fElevMin)*diffInv;
			e *= e;

			// Clamp value to [0.25, 0.75]
			e = e*0.5f + 0.25f;
 		}
	}
}

/*----------------------------------------------------------------------------*/
unsigned int TerrainTexture::getTextureId() { return uiTexId; }
